/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package tpcompiladores;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Enumeration;

/**
 *
 * @author Gaston
 */
public class Generador {
    
    private PolacaInversa polaca;
    private ArrayList<String> pila;
    private ArrayList<Integer> etiquetas;
    private int numVar;
    private BufferedWriter writer;
    private TablaSimb table;
    private Boolean fatalError;
    private int polacaPos=-1;
    String eol = System.getProperty("line.separator");
    //private int numEt = 0;
    
    public Generador(PolacaInversa pi,TablaSimb table){
        
        if(pi.getError()){
            //La polaca se termino con errores léxico-sintácticos. ABORT
            System.out.println("Fatal Error");
            this.fatalError = true;
            return;            
        }
        
        fatalError = false;
        
        polaca = pi;
        etiquetas = new ArrayList<Integer>();
        this.table = table;
        pila = new ArrayList<>();
        numVar = 0;
        try{
            File file = new File("assembler.asm");
            FileWriter fw = new FileWriter(file.getAbsoluteFile());
            writer = new BufferedWriter(fw);
            String header = generarEncabezado();  
            String code = generar();
            String tags = generarTags();            
            String vars = generarVariables(table);            
            writer.write(header);
            writer.write(vars);  
            writer.write(code);  
            writer.write(tags);  
            writer.close();
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }

    public Boolean getFatalError() {
        return fatalError;
    }
    
    private String generarTags(){
        String r = "";  
        r += eol + "DIV0:" + eol + "invoke MessageBox, NULL, addr EDIV0, addr nombre, MB_OK" +eol + "JMP ABORT";
        r += eol + "OVER:" + eol + "invoke MessageBox, NULL, addr EOVER, addr nombre, MB_OK" +eol + "JMP ABORT";
        r += eol + "ABORT:" + eol + "invoke ExitProcess,1"; 
        r += eol + "end start";
        return r;
    }
   
    private String generarEncabezado(){
        String encab = ".386";
        encab += eol + ".model flat, stdcall";
        encab += eol + "option casemap :none";
        encab += eol + "include \\masm32\\include\\windows.inc";
        encab += eol + "include \\masm32\\include\\kernel32.inc";
        encab += eol + "include \\masm32\\include\\user32.inc";
        encab += eol + "includelib \\masm32\\lib\\kernel32.lib";
        encab += eol + "includelib \\masm32\\lib\\user32.lib";
        encab += eol + "include    \\masm32\\include\\masm32.inc";
        encab += eol + "includelib \\masm32\\lib\\masm32.lib";
        encab += eol + "include    \\masm32\\include\\debug.inc";
        encab += eol + "includelib \\masm32\\lib\\debug.lib";
        encab += eol + ".data";
        return encab;
    }
    
    private String generarVariables(TablaSimb t){
        String key;
        String code = "";   
        code += eol + "nombre db \"Compilador\",0";
        code += eol + "EDIV0 db \"Error: Division por 0\",0";
        code += eol + "EOVER db \"Error: Overflow en la suma\",0";
        code += eol + "cte0 dq 0";
        Enumeration<String> keys = t.table.keys();
        while(keys.hasMoreElements()){
            key = keys.nextElement();
            if(t.isVar(key)){
                if(t.table.get(key).getValue().equals(""))
                    code += eol + "_"+ key + " dq " + "0000h";
                else
                    code += eol + "_"+ key + " dq " + t.table.get(key).getValue();
            }
            else if(t.isArray(key)){
                String var = t.getLim(key);
                int piso = (int) Math.floor(Double.parseDouble(var));
                code += eol + "_"+key + " dq " + piso + " dup (0000h)";
            }
            else if(key.contains("cte")){
                code += eol + key + " dq " + t.table.get(key).getValue();
            }else if(key.contains("CADENA")){
                code += eol + key + " db " + t.table.get(key).getValue() + ", 0";
            }       
           
        }
        code += eol + eol + ".code";
        code += eol + "start:";
        return code;
    }
    
    private int numCad=0;
    
    private String genPrint(String actual){
        String code = "";
        String s = actual.substring(7);
        code += eol + "invoke MessageBox, NULL, addr CADENA"+numCad +" , addr nombre, MB_OK";
        table.putSymbol("CADENA" +numCad, new RegTs("CADENA"+numCad,s));
        numCad++;
        return code;        
    }
    
        
    public String genSalto(String tipo, String v1){
        String code = "";
        code += eol + tipo + " ET" + v1;
        
        if(etiquetas.contains(Integer.valueOf(v1))){
            return code;
        }
        
        if(etiquetas.indexOf(Integer.valueOf(v1)) == -1){
            etiquetas.add(Integer.valueOf(v1));    
        }
        return code;
    }
    
    private int doTags = 0;
    public String generar(){
        String code = "";
        int pos = 0;
        String actual = polaca.desapilarPolaca();
        String result;
        String salto;
        while(actual != null){
            
            if(actual.equals("DO")){
                etiquetas.add(pos);
            }
            if(etiquetas.contains(pos)){
                int index = etiquetas.indexOf(pos);
                etiquetas.remove(index);
                code +=  eol +"ET" + pos +":";
            }
            if(actual.equals("DO")){
                etiquetas.add(pos);
            }

            if(!isOperador(actual)){
                salto = isSalto(actual);
                if(salto == null){
                    if(actual.contains("\"")){
                        code += genPrint(actual);
                    }else
                    { 
                        if(actual.equals("ABORT"))
                            code += eol + "JMP ABORT";
                        else pila.add(actual);                        
                    }
                }
                else{
                    switch(salto){
                        case("JNE"):code+=genSalto("JNE",pila.remove(pila.size()-1));
                            break;
                        case("JE"):code+=genSalto("JE",pila.remove(pila.size()-1));
                            break; 
                        case("JB"):code+=genSalto("JB",pila.remove(pila.size()-1));
                            break; 
                        case("JBE"):code+=genSalto("JBE",pila.remove(pila.size()-1));
                            break; 
                        case("JA"):code+=genSalto("JA",pila.remove(pila.size()-1));
                            break; 
                        case("JAE"):code+=genSalto("JAE",pila.remove(pila.size()-1));
                            break; 
                        case("JMP"):code+=genSalto("JMP",pila.remove(pila.size()-1));
                            break;
                        default: System.out.println("OTRO");
                   }
                }
            }
            
            else{
                code += gen(actual,pila.remove(pila.size()-1),pila.remove(pila.size()-1));                
            }
            

            actual = polaca.desapilarPolaca();
            pos++;
        } 
        
       if(etiquetas.size() > 0)
         code += eol + "ET"+ etiquetas.get(etiquetas.size()-1)+":";

        while(!etiquetas.isEmpty()){
            System.out.println(etiquetas.get(0));
            etiquetas.remove(0);
        }
         code += eol + "invoke ExitProcess,0";
        return code;
    }
   
    
    public String getTipo(String param){            
        if(param.contains("ARRAY"))
            return "ARRAY";
        if((table.exists(param))||(table.exists(table.getCte(param)))){
            if (table.isVar(param))
                return "DOUBLE";
            else return "CTE";
        }
        else return "UNK";
    }
    
    public String concatLoad(String var){
        String tipo = getTipo(var);
        if(tipo.equals("DOUBLE"))
            return( eol +"FLD " + "_"+ var);
        else if(tipo.equals("CTE"))
            return( eol + "FLD " + table.getCte(var));
        else if(tipo.equals("ARRAY")){
            String result = "";
            result += genArrayLocation(var)[0];
            result += eol + "FLD qword ptr [_@"+numVar+"]";
            result += eol + "FWAIT";
            result += eol + "FSTP _@"+numVar;
            result += eol + "FLD _@"+numVar;     
            numVar++;
            return result;
        }        
        return "ERROR";
    }
    
    //Guarda la posicion del array a la que se quiere acceder en pos
    private String[] genArrayLocation(String array){
        String[] result = {"",""};
        String[] Stringarray = array.split(",");
        //System.out.println(Stringarray[0]+" | "+Stringarray[1]+" | "+Stringarray[2] + " | " + Stringarray[3]);       
        result[0] += eol + "FLD "+ "_"+Stringarray[1];
        result[0] += eol + "FADD _@" + numVar;  
        result[0] += eol + "FSTP _@" + numVar;
        result[1] = "_@"+numVar;        
        table.putSymbol("@" + numVar, new RegTs("DOUBLE",Stringarray[3])); 
        return result;
    }
    
    public String asig(String var1, String var2){
        String result = "";
        if(var2.contains("ARRAY")){
            String[] arrayStuff = genArrayLocation(var2);
            numVar++;
            result += eol + arrayStuff[0];
            result += concatLoad(var1);
            result += eol + "FSTP qword ptr["+arrayStuff[1]+"]";
            result += eol + "FWAIT";
        }else{
            result += concatLoad(var1);
            result += eol + "FSTP " + "_"+var2;
        }
        return result;        
    }
    
    
    public String sumar(String var1, String var2){
        String result = "";
        result += concatLoad(var1);
        result += concatLoad(var2);
        result += eol + "FCOMI ST,ST(1)" + eol + "JC OVER";
        result += eol + "FADD";
        result += eol + "FSTP _@" + numVar;
        //result += eol + "JC OVER";
        table.putSymbol("@" + numVar, new RegTs("DOUBLE",""));
        pila.add("@" + String.valueOf(numVar));
        return result;
    }
    
    public String restar(String var1, String var2){
        String result = "";
        result += concatLoad(var1);
        result += concatLoad(var2);
        result += eol + "FSUB";
        result += eol + "FSTP _@" + numVar;
        table.putSymbol("@" + numVar, new RegTs("DOUBLE",""));
        pila.add("@" + String.valueOf(numVar));
        return result;
    }
    
    public String multiplicar(String var1, String var2){
        String result = "";
        result += concatLoad(var1);
        result += concatLoad(var2);
        result += eol + "FMUL";
        result += eol + "FSTP _@" + numVar;
        table.putSymbol("@" + numVar, new RegTs("DOUBLE",""));
        pila.add("@" + String.valueOf(numVar));
        return result;
    }
    
    public String div(String var1, String var2){
        String result = "";
        result += concatLoad(var2);
        result += eol + "FLD cte0";
        result += eol + "FCOMP"+eol + "fstsw ax" +eol +"sahf";
        result += eol + "JE DIV0";       
        result += concatLoad(var1);
        result += concatLoad(var2);
        result += eol + "FDIV";
        result += eol + "FSTP _@" + numVar;
        table.putSymbol("@" + numVar, new RegTs("DOUBLE",""));
        pila.add("@" + String.valueOf(numVar));
        return result;
    }
    

    /*
    public String mayor(String var1, String var2){
        String result = "";
        result += concatLoad(var2);
        result += concatLoad(var1);
        result += eol + "FCOM";
        //result += eol + "JAE";
        return result;
    }
    
    public String mayorIgual(String var1, String var2){
        String result = "";
        result += concatLoad(var1);
        result += concatLoad(var2);
        result += eol + "FCOM";
        //result += eol + "JA";
        return result;
    }
    
    public String menor(String var1, String var2){
        String result = "";
        result += concatLoad(var1);
        result += concatLoad(var2);
        result += eol + "FCOM";
        //result += eol + "VERQUEJMP ET" + numEt;
        return result;
    }
    
    public String menorIgual(String var1, String var2){
        String result = "";
        result += concatLoad(var1);
        result += concatLoad(var2);
        result += eol + "FCOM";
        //result += eol + "JB";
        return result;
    }
    
    public String igual(String var1, String var2){
        String result = "";
        result += concatLoad(var1);
        result += concatLoad(var2);
        result += eol + "FCOM";
        //result += eol + "JMP ET" + numEt;
        return result;
    }
    
    public String distinto(String var1, String var2){
        String result = "";
        result += concatLoad(var1);
        result += concatLoad(var2);
        result += eol + "FCOM";
        result += eol + "!=";
        return result;
    }
    */
    
    public String comparacion(String var1, String var2){
        String result = "";
        result += concatLoad(var2);
        result += concatLoad(var1);
        result += eol + "FCOMP"+eol + "fstsw ax" +eol +"sahf";
        return result; 
    }
    
    public String gen(String op, String v1, String v2){
        numVar++;
        String toSave = "";
        switch(op){
            case(":="):toSave = asig(v1,v2);
                break;
            case("+"): toSave = sumar(v2,v1);
                break;
            case("-"): toSave = restar(v2,v1);
                break;
            case("*"): toSave = multiplicar(v2,v1);
                break;        
            case("/"): toSave = div(v2,v1);
                break;
            case("=="): toSave = comparacion(v2,v1);
                break;        
            case("<>"): toSave = comparacion(v2,v1);
                break;
            case(">"): toSave = comparacion(v2,v1);
                break;        
            case("<"): toSave = comparacion(v2,v1);
                break;
            case(">="): toSave = comparacion(v2,v1);
                break; 
            case("<="): toSave = comparacion(v2,v1);
                break;
            default: System.out.println("OTRO");            
        }
        
        return toSave;
    }

    
    public String isSalto(String s){
        String saltos = "JEJNEJAJAEJBJBEJMP";
        if(saltos.contains(s))
            return s;
        else return null;
    }
    
    public boolean isOperador(String s){
        String operador = "+-*/:=>=<=<>==";
        if(operador.contains(s))
            return true;
        return false;
    }
    
    public void imprimir(){
        for(String s:pila)
            System.out.println(s);
    }
    
}
